struct S
{
    int a;
    int b;
    int c;

    bool test(int a_, int b_, int c_) inout
    {
        return a_ == a && b_ == b && c_ == c;
    }
}

__gshared S globalS = S(2, 3, 4);
__gshared S globalS2 = S();
immutable S immutableS = S(9, 8, 7);

S getVal()
{
    // Returns by value
    return globalS;
}

ref S getRef()
{
    // Returns by reference
    return globalS;
}

ref S getRefRvalue() __rvalue
{
    // Takes ownership of globalS
    return globalS;
}

S getImmutable()
{
    return immutableS;
}

__gshared int globalDtorCount;

struct D
{
    int d;

    this(D rhs)
    {
        d = rhs.d;
        rhs.d = 0xdeadbeef;
    }

    this(ref D rhs)
    {
        d = 12345;
    }

    ~this()
    {
        if (d == 123456)
            globalDtorCount++;
        d = 0xdeadbeef;
    }
}

__gshared D globalD = D(123456);
__gshared D globalD2 = D(10000);

void resetD()
{
    globalDtorCount = 0;
    globalD.d = 123456;
    globalD2.d = 10000;
}

D getD()
{
    // Returns by value
    return globalD;
}

D getRvalueD()
{
    // Returns by moving globalD, but should not have reference semantics
    // Tests if the inliner incorrectly applies reference semantics
    return __rvalue(globalD);
}

ref D getRefD()
{
    // Returns by reference
    return globalD;
}

ref D getRefRvalueD() __rvalue
{
    // Takes ownership of globalS
    return globalD;
}

/************************************/
// Test inlining of return by value

S funcVal1()
{
    return getVal();
}

S funcVal2()
{
    return getRef();
}

S funcVal3()
{
    return getRefRvalue();
}

S funcVal4()
{
    return globalS.a ? immutableS : globalS;
}

S funcVal5()
{
    return globalS.a ? globalS : S();
}

D funcVal6()
{
    return getD();
}

D funcVal7()
{
    return getRvalueD();
}

D funcVal8()
{
    return getRefD();
}

D funcVal9()
{
    return getRefRvalueD();
}

D funcVal10()
{
    return __rvalue(globalS.a ? globalD : D());
}

void consumeD(D d) {}

void testValueReturn()
{
    // Returning by value should not change the source object
    getVal().a++;
    assert(globalS.test(2, 3, 4));
    getImmutable().a++;
    assert(immutableS.test(9, 8, 7));
    funcVal1().a++;
    assert(globalS.test(2, 3, 4));
    funcVal2().c++;
    assert(globalS.test(2, 3, 4));
    funcVal3().c++;
    assert(globalS.test(2, 3, 4));
    funcVal4().a++;
    assert(immutableS.test(9, 8, 7));
    funcVal5().a++;
    assert(globalS.test(2, 3, 4));

    consumeD(getD());
    assert(globalD.d == 123456);
    assert(globalDtorCount == 0);

    consumeD(__rvalue(getD()));
    assert(globalD.d == 123456);
    assert(globalDtorCount == 0);

    consumeD(getRvalueD());
    assert(globalD.d == 0xdeadbeef);
    assert(globalDtorCount == 1);
    resetD();

    consumeD(__rvalue(getRvalueD()));
    assert(globalD.d == 0xdeadbeef);
    assert(globalDtorCount == 1);
    resetD();

    consumeD(funcVal6());
    assert(globalD.d == 123456);
    assert(globalDtorCount == 0);

    consumeD(__rvalue(funcVal6()));
    assert(globalD.d == 123456);
    assert(globalDtorCount == 0);

    consumeD(funcVal7());
    assert(globalD.d == 0xdeadbeef);
    assert(globalDtorCount == 1);
    resetD();

    consumeD(__rvalue(funcVal7()));
    assert(globalD.d == 0xdeadbeef);
    assert(globalDtorCount == 1);
    resetD();

    consumeD(funcVal8());
    assert(globalD.d == 123456);
    assert(globalDtorCount == 0);

    consumeD(__rvalue(funcVal8()));
    assert(globalD.d == 123456);
    assert(globalDtorCount == 0);

    consumeD(funcVal9());
    assert(globalD.d == 0xdeadbeef);
    assert(globalDtorCount == 1);
    resetD();

    consumeD(__rvalue(funcVal9()));
    assert(globalD.d == 0xdeadbeef);
    assert(globalDtorCount == 1);
    resetD();

    consumeD(funcVal10());
    assert(globalD.d == 123456);
    assert(globalDtorCount == 0);

    consumeD(__rvalue(funcVal10()));
    assert(globalD.d == 123456);
    assert(globalDtorCount == 0);
}


/************************************/
// Test inlining of return by reference

ref S funcRef1()
{
    return getRef();
}

ref S funcRef2()
{

    return globalS.a ? globalS : globalS2;
}

ref D funcRef3()
{
    return getRefD();
}

ref D funcRef4()
{
    return __rvalue(globalS.a ? globalD : globalD2);
}

void consumeRefD(ref D d)
{
    assert(d.d == 123456);
    consumeD(__rvalue(d));
    assert(d.d == 0xdeadbeef);
}

void testRefReturn()
{
    // Returning by ref should mutate the source object
    funcRef1().a++;
    assert(globalS.test(3, 3, 4));
    funcRef2().c++;
    assert(globalS.test(3, 3, 5));

    // Passing ref to value parameter should trigger a copy
    resetD();
    consumeD(funcRef3());
    assert(globalD.d == 123456);
    assert(globalDtorCount == 0);

    // ... but not if there is __rvalue
    consumeD(__rvalue(funcRef3()));
    assert(globalD.d == 0xdeadbeef);
    assert(globalDtorCount == 1);

    // ditto
    resetD();
    consumeD(funcRef4());
    assert(globalD.d == 123456);
    assert(globalDtorCount == 0);
    consumeD(__rvalue(funcRef4()));
    assert(globalD.d == 0xdeadbeef);
    assert(globalDtorCount == 1);

    resetD();
    consumeRefD(funcRef3());

    resetD();
    consumeRefD(funcRef4());
}

/************************************/
// Test inlining of return by rvalue reference

ref S funcRvalueRef1() __rvalue
{
    return getRef();
}

ref S funcRvalueRef2() __rvalue
{
    return globalS.a ? globalS : globalS2;
}

ref D funcRvalueRef3() __rvalue
{
    return getRefD();
}

ref D funcRvalueRef4() __rvalue
{
    return globalS.a ? globalD : globalD2;
}

void consumeRvalueRefD(D d)
{
    assert(d.d == 123456);
    consumeD(__rvalue(d));
    assert(d.d == 0xdeadbeef);
}

void testRvalueRefReturn()
{
    // rvalue ref is ref
    funcRvalueRef1().a++;
    assert(globalS.test(4, 3, 5));
    funcRvalueRef2().c++;
    assert(globalS.test(4, 3, 6));

    resetD();
    consumeRvalueRefD(funcRvalueRef3());

    resetD();
    consumeRvalueRefD(funcRvalueRef4());
}

/************************************/
// https://github.com/dlang/dmd/issues/22157
class Test22157
{
    override string toString()
	{
        auto e = { return cast() super; } ();
        return null;
    }
}

/************************************/

void main()
{
    testValueReturn();
    testRefReturn();
    testRvalueRefReturn();
}
